#include "LevelManager.h"


#include <OgreRoot.h>
#include <OgreViewport.h>
#include <OgreSceneManager.h>



#include "OgreFramework.h"

//#include "Maths.h"
#include "Util.h"


#define DEBUG_LISTS 0
#define DEBUG_LIST_LENGTHS_ONLY 0

LevelManager::LevelManager()
{
    //ctor
    m_isInitialized=false;
    m_blockFactory = 0;
    //  m_pather=0;


    //initialisation du pathfinding
    m_pather = new micropather::MicroPather( this );
    m_isInitialized=true;
}

LevelManager::~LevelManager()
{
    //dtor
    clear();
    if(m_blockFactory)delete m_blockFactory;
    if(m_pather)delete m_pather;
}
/** @brief clear
  *
  * @todo: document this function
  */
void LevelManager::clear()
{





    int i,j;


    if (m_isInitialized)
    {


        for (i=0; i<m_arrayBlocks.size(); i++)
        {
            for (j=0; j<m_arrayBlocks[i].size(); j++)
            {
                delete m_arrayBlocks[i][j];
            }
            m_arrayBlocks[i].clear();
        }
        m_arrayBlocks.clear();

        // m_pather->Reset();

        delete m_blockFactory;
        m_blockFactory = 0;
        m_isInitialized=false;
    }
}

/** @brief update
  *
  * @todo: document this function
  */
void LevelManager::update(double timeSinceLastFrame)
{

}

/** @brief init
  *
  * @todo: document this function
  */
void LevelManager::init()
{
    if (!m_isInitialized)
    {

        m_blockFactory = new LevelBlockFactory(this);



    }
    else
        OgreFramework::getSingletonPtr()->m_pLog->logMessage("!!!!!   LevelManager already initialized");



}


/** @brief createRandomLevel
  *
  * @todo: document this function
  */
void LevelManager::createRandomLevel(RandomMapGenerationParams _params)
{

    int i,j;


//level random with perlin noise
    Ogre::SceneManager *sceneMgr = OgreFramework::getSingletonPtr()->m_pRoot->getSceneManager("GameSceneMgr");
    std::vector <LevelBlock*> tmp;


    for (i=0; i<_params.size; i++)
    {
        for (j=0; j<_params.size; j++)
        {
            float r = PerlinUtil::bruit_coherent2D(i,j,0.01,1);

            if (r<=0.3)
            {
                tmp.push_back(m_blockFactory->createGenericBlock("../data/block/square1.cfg",i,j));
            }
            else     if (r>0.3 && r<=0.5)
            {
                tmp.push_back(m_blockFactory->createGenericBlock("../data/block/square2.cfg",i,j));
            }
            else     if (r>0.5 && r<=0.55)
            {
                tmp.push_back(m_blockFactory->createGenericBlock("../data/block/square3.cfg",i,j));
            }
            else
            {
                tmp.push_back(m_blockFactory->createGenericBlock("../data/block/square4.cfg",i,j));
            }



        }
        m_arrayBlocks.push_back(tmp);
        tmp.clear();
    }



sceneMgr->setSkyBox(true, "Examples/SpaceSkyBox",500, true);

}


/** @brief position2index
  *
  * @todo: document this function
  */
void LevelManager::position2index(const float & x, const float & y, float *i, float *j)
{

}

/** @brief setBlockAt
  *
  * @todo: document this function
  */
void LevelManager::setBlockAt(std::string name, int i, int j)
{
    /*
        LevelBlock* oldBlock = m_arrayBlocks[i][j];
        if (oldBlock!=0)
        {
            oldBlock->clear();
            delete oldBlock;
        }*/


    LevelBlock* newBlock = m_blockFactory->createGenericBlock(name, i, j);
    m_arrayBlocks[i][j] = newBlock;

}
/** @brief getBlockAt
  *
  * @todo: document this function
  */
LevelBlock* LevelManager::getBlockPtrAt(float x, float y)
{
    int i = int(x+0.5);
    int j = int(y+0.5);
    return getBlockPtrAt( i,  j);
}
/** @brief getBlockAt
  *
  * @todo: document this function
  */
LevelBlock* LevelManager::getBlockPtrAt(int i, int j)
{

    if (i<0 || j<0 || i>=m_arrayBlocks.size() || j>=m_arrayBlocks[i].size())
        return 0;
    else
        return m_arrayBlocks[i][j];
}
LevelBlock* LevelManager::getBlockPtrAt(Ogre::Vector2 p)
{
    getBlockPtrAt(p.x,p.y);
}

LevelBlock* LevelManager::nodeToBlock( void* node)
{
    return (LevelBlock*)node;

}
void* LevelManager::blockToNode(LevelBlock* block)
{
    return (void*)block;
}

Ogre::Vector2 LevelManager::blockToVector(LevelBlock* block)
{
    return block->getPosition();
}
LevelBlock* LevelManager::vectorToBlock(Ogre::Vector2 vector)
{
    //OgreFramework::getSingletonPtr()->m_pLog->logMessage("vectorToBlock  :  vec  "+Ogre::StringConverter::toString(vector));
    //OgreFramework::getSingletonPtr()->m_pLog->logMessage("vectorToBlock  :  block  "+Ogre::StringConverter::toString(getBlockPtrAt(vector.x,vector.y)->getPosition()));
    return getBlockPtrAt(vector.x,vector.y);
}




/** @brief searchPath
  *
  * @todo: document this function
  */
std::list<Ogre::Vector2> LevelManager::searchPath(Ogre::Vector2 start, Ogre::Vector2 end)
{
    m_pather = new micropather::MicroPather( this );

    OgreFramework::getSingletonPtr()->m_pLog->logMessage("Search Path between "+Ogre::StringConverter::toString(start)
            +"  and  "+Ogre::StringConverter::toString(end));

    std::list<Ogre::Vector2> listPoints;
    listPoints.clear();
    std::vector<void*> path;

    float totalCost;



    int result = m_pather->Solve( blockToNode(vectorToBlock(start)), blockToNode(vectorToBlock(end)), &path, &totalCost );


    OgreFramework::getSingletonPtr()->m_pLog->logMessage("Pather solve...");

    if ( result == micropather::MicroPather::NO_SOLUTION )
    {
        OgreFramework::getSingletonPtr()->m_pLog->logMessage("Pather don't find solution...");
    }
    if ( result == micropather::MicroPather::SOLVED )
    {
        OgreFramework::getSingletonPtr()->m_pLog->logMessage("Pather succeed...");


        std::vector<void*>::iterator it;
        int i = 0;

        for ( it=path.begin()+1 ; it != path.end(); it++ )
        {
            listPoints.push_back(blockToVector(nodeToBlock(*it)));
            OgreFramework::getSingletonPtr()->m_pLog->logMessage("push waypoints "+Ogre::StringConverter::toString(blockToVector(nodeToBlock(*it))));

        }

    }

    delete m_pather;



    return listPoints;
}

bool LevelManager::isBlockCrossable(float x, float y)
{
    bool r=false;

    LevelBlock* block = 0;
    block = getBlockPtrAt(x,y);
    if (block!=0)
    {
        r = block->isCrossable();
    }

    return r;
}
/** @brief setBlockCrossable
  *
  * @todo: document this function
  */
void LevelManager::setBlockCrossable(float x, float y, bool _val )
{
    LevelBlock* block = 0;
    block = getBlockPtrAt(x,y);
    if (block!=0)
    {
        block->setCrossable(_val);
    }

}




/** @brief initPathFinding
  *
  * @todo: document this function
  */
void LevelManager::initPathFinding()
{

}

/** @brief loadMap
  *
  * @todo: document this function
  */
void LevelManager::loadMap(std::string path)
{

}

/** @brief saveMap
  *
  * @todo: document this function
  */
void LevelManager::saveMap(std::string path)
{





}

/** @brief PrintStateInfo
  *
  * @todo: 	This function is only used in DEBUG mode - it dumps output to stdout. Since void*
			aren't really human readable, normally you print out some concise info (like "(1,2)")
			without an ending newline.
  */
void LevelManager::PrintStateInfo(void* state)
{

}

/** @brief AdjacentCost
  *
  * @todo: 	Return the exact cost from the given state to all its neighboring states. This
			may be called multiple times, or cached by the solver. It *must* return the same
			exact values for every call to MicroPather::Solve(). It should generally be a simple,
			fast function with no callbacks into the pather.
  */
void LevelManager::AdjacentCost(void* state, std::vector< micropather::StateCost > *adjacent)
{
    Ogre::Vector2 pos = blockToVector(nodeToBlock(state));
    float cost=1.0f;
    Ogre::Vector2 vois = Ogre::Vector2(0,0);


    micropather::StateCost nodeCost;
    LevelBlock* b=0;

    vois.x=pos.x+1;
    vois.y=pos.y;
    if (isBlockCrossable(vois.x, vois.y))
    {
        //OgreFramework::getSingletonPtr()->m_pLog->logMessage("block right crossable");
        nodeCost = { blockToNode(vectorToBlock(vois)), cost };
        adjacent->push_back(nodeCost);
    }

    vois.x=pos.x-1;
    vois.y=pos.y;
    if (isBlockCrossable(vois.x, vois.y))
    {
        // OgreFramework::getSingletonPtr()->m_pLog->logMessage("block left crossable");
        nodeCost = { blockToNode(vectorToBlock(vois)), cost };
        adjacent->push_back(nodeCost);
    }

    vois.x=pos.x;
    vois.y=pos.y+1;
    if (isBlockCrossable(vois.x, vois.y))
    {
        // OgreFramework::getSingletonPtr()->m_pLog->logMessage("block up crossable");
        nodeCost = { blockToNode(vectorToBlock(vois)), cost };
        adjacent->push_back(nodeCost);
    }

    vois.x=pos.x;
    vois.y=pos.y-1;
    if (isBlockCrossable(vois.x, vois.y))
    {
        nodeCost = { blockToNode(vectorToBlock(vois)), cost };
        adjacent->push_back(nodeCost);
    }

    /*
        cost=1.3f;
        // diagonale
        vois.x=pos.x+1;
        vois.y=pos.y+1;
        if (isBlockCrossable(vois.x, vois.y))
        {
            nodeCost = { blockToNode(vectorToBlock(vois)), cost };
            adjacent->push_back(nodeCost);
        }

        vois.x=pos.x+1;
        vois.y=pos.y-1;
        if (isBlockCrossable(vois.x, vois.y))
        {
            nodeCost = { blockToNode(vectorToBlock(vois)), cost };
            adjacent->push_back(nodeCost);
        }

        vois.x=pos.x-1;
        vois.y=pos.y+1;
        if (isBlockCrossable(vois.x, vois.y))
        {
            nodeCost = { blockToNode(vectorToBlock(vois)), cost };
            adjacent->push_back(nodeCost);
        }

        vois.x=pos.x-1;
        vois.y=pos.y-1;
        if (isBlockCrossable(vois.x, vois.y))
        {
            nodeCost = { blockToNode(vectorToBlock(vois)), cost };
            adjacent->push_back(nodeCost);
        }

    */


}

/** @brief LeastCostEstimate
  *
  *   	Return the least possible cost between 2 states. For example, if your pathfinding
			is based on distance, this is simply the straight distance between 2 points on the
			map. If you pathfinding is based on minimum time, it is the minimal travel time
			between 2 points given the best possible terrain.
  */
float LevelManager::LeastCostEstimate(void* stateStart, void* stateEnd)
{
    Ogre::Vector2 start = blockToVector(nodeToBlock(stateStart));
    Ogre::Vector2 end = blockToVector(nodeToBlock(stateEnd));

    //OgreFramework::getSingletonPtr()->m_pLog->logMessage("\nLeastCostEstimate : pos start : "+Ogre::StringConverter::toString(start));
    // OgreFramework::getSingletonPtr()->m_pLog->logMessage("end : "+Ogre::StringConverter::toString(end));

    // OgreFramework::getSingletonPtr()->m_pLog->logMessage("LeastCostEstimate : "+Ogre::StringConverter::toString(start.distance(end)));

    return start.distance(end);

}

void LevelManager::saveToXMLFile(Ogre::String _path)
{
    OgreFramework::getSingletonPtr()->m_pLog->logMessage("Save Level To XML");

    TiXmlDocument doc;

    TiXmlDeclaration * decl = new TiXmlDeclaration( "1.0", "", "" );
    doc.LinkEndChild( decl );

    TiXmlElement * element = new TiXmlElement( "Level" );
    saveToXML(element);
    doc.LinkEndChild(element);
    doc.SaveFile( _path.c_str());
}

void LevelManager::saveToXML(TiXmlElement* parentElement)
{





    TiXmlElement * element = new TiXmlElement( "Map" );
    parentElement->LinkEndChild( element );

    element->SetAttribute("width",m_arrayBlocks.size());
    element->SetAttribute("height",m_arrayBlocks[0].size());

    int i=0;
    int j=0;
    TiXmlElement* blockXML;

    for (i=0; i<m_arrayBlocks.size(); i++)
    {
        for (j=0; j<m_arrayBlocks[i].size(); j++)
        {
            blockXML = new TiXmlElement("block");
            blockXML->SetAttribute("x",Ogre::StringConverter::toString(i).c_str());
            blockXML->SetAttribute("y",Ogre::StringConverter::toString(j).c_str());
            blockXML->SetAttribute("type",getBlockPtrAt(i,j)->getName().c_str());

            if (getBlockPtrAt(i,j)->isCrossable())
                blockXML->SetAttribute("crossable","true");
            else
                blockXML->SetAttribute("crossable","false");

            element->LinkEndChild(blockXML);

            // delete blockXML;

        }
    }

}



bool LevelManager::loadFromXML(TiXmlElement* parentElement)
{

    TiXmlNode *mapXML = parentElement->FirstChild( "Map" );


    int width = atoi(mapXML->ToElement()->Attribute("width"));
    int height = atoi(mapXML->ToElement()->Attribute("height"));

    m_arrayBlocks.resize(width);
    int i;
    for (i=0; i<width; i++)
        m_arrayBlocks[i].resize(height);



    TiXmlNode* blockXML = mapXML->FirstChild( "block" );

    while( blockXML != NULL )
    {


        int x = atoi(blockXML->ToElement()->Attribute("x"));
        int y = atoi(blockXML->ToElement()->Attribute("y"));
        std::string name = blockXML->ToElement()->Attribute("type");
        bool crossable = string2bool(blockXML->ToElement()->Attribute("crossable"));

        setBlockAt(name,x,y);
        getBlockPtrAt(x,y)->setCrossable(crossable);

        blockXML = parentElement->IterateChildren("block",blockXML);

    }

    return true;
}


/** @brief loadFromXMLFile
  *
  * @todo: document this function
  */
bool LevelManager::loadFromXMLFile(Ogre::String _path)
{
    OgreFramework::getSingletonPtr()->m_pLog->logMessage("Load Level To XML"+_path);

    TiXmlDocument doc(_path.c_str());
    bool loadOkay = doc.LoadFile();
    if (loadOkay)
    {
        TiXmlElement *pRoot;
        pRoot = doc.FirstChildElement( "Level" );
        if ( pRoot )
        {

            loadFromXML(pRoot);
            // pParm = pParm->NextSiblingElement( "Parameter" );

        }


    }
}

/** @brief registerBuilding
  *
  * @todo: document this function
  */
void LevelManager::registerBuilding(BuildingObject* _building)
{

}

